﻿/****************************************************************************
** file: matUtils.cpp
** brief: cv::Mat 的一些常用功能代码
** Copyright (C) LiYuan
** Author: LiYuan
** E-Mail: 18069211#qq(.)com
** Version 2.0.1
** Last modified: 2021.01.25
** Modified By: LiYuan
****************************************************************************/

#include "matUtils.h"
#include <opencv2/imgproc.hpp>
#include <random>

void detectHSColor(const cv::Mat &image,
                   double minHue, double maxHue,
                   double minSat, double maxSat,
                   cv::Mat &mask)
{
    cv::Mat hsv;
    cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);
    std::vector<cv::Mat> channels;
    cv::split(hsv, channels);
    cv::Mat mask1, mask2, hueMask;
    cv::threshold(channels[0], mask1, maxHue, 255, cv::THRESH_BINARY_INV);
    cv::threshold(channels[0], mask2, minHue, 255, cv::THRESH_BINARY);
    if(minHue < maxHue)
    {
        hueMask = mask1 & mask2;
    }
    else
    {
        hueMask = mask1 | mask2;
    }
    cv::Mat satMask;
    cv::inRange(channels[1], minSat, maxSat, satMask);
    mask = hueMask & satMask;
}

bool colorImage2GrayImage(const cv::Mat &inImage, cv::Mat &outImage)
{
    switch (inImage.channels())
    {
    case 1:
        outImage = inImage.clone();
        break;
    case 3:
        cv::cvtColor(inImage, outImage, cv::COLOR_RGB2GRAY);
        return true;
    case 4:
        cv::cvtColor(inImage, outImage, cv::COLOR_RGBA2GRAY);
        return true;
    default:
        break;
    }
    return false;
}

std::vector<cv::Vec3b> buildRandomColormap(int N)
{
    cv::RNG rng;
    std::vector<cv::Vec3b> colormap;
    for(int i = 0; i < N; i++)
    {
        colormap.push_back( cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)) );
    }
    return colormap;
}

void drawPoints(cv::Mat img, std::vector<cv::Point2d> pts, cv::Scalar color)
{
    int N = pts.size();
    for(int i = 0; i < N; i++)
    {
        img.at<cv::Scalar>(pts[i]) = color;
    }

}
cv::Mat imageFromColormap(const cv::Mat &grayImage, const std::vector<cv::Vec3b> &colormap)
{
    cv::Mat out(grayImage.size(), CV_8UC3);
    out = cv::Scalar(0, 0, 0);
    int N = colormap.size();
    //cv::Mat_<cv::Vec3b> img(out);
    for(int row = 0; row < grayImage.rows; row ++)
    {
        cv::Vec3b * pDest = out.ptr<cv::Vec3b>(row);
        const uchar *pSrc = grayImage.ptr<const uchar>(row);
        for(int col = 0; col < grayImage.cols; col ++)
        {
            if(pSrc[col] >= N)
            {
                pDest[col] = cv::Vec3b(pSrc[col], pSrc[col], pSrc[col]);
            }
            else
            {
                pDest[col] = colormap[pSrc[col]];
            }
        }
    }
    return out;
}

std::vector<cv::Point2i> getEdgePoints(cv::Mat &contours, int threshold)
{
    std::vector<cv::Point2i> points;
    for(int row = 0; row < contours.rows; row++)
    {
        const uchar * pLine = contours.ptr<const uchar>(row);
        for(int col = 0; col < contours.cols; col++)
        {
            if(pLine[col] > threshold)
            {
                points.push_back(cv::Point2i(col, row));
            }
        }
    }
    return points;
}

/**
 * @brief drawContours 在 bgr 上将 contours 上的不为零的像素点填充为 color
 * @param bgr 要求是 3 通道的图像，或者4通道图像
 * @param contours
 * @param color
 */
void drawContours(cv::Mat bgr, cv::Mat contours, cv::Vec3b color)
{
    if(bgr.channels() == 3)
    {
        for (int j = 0; j < bgr.rows; j++)
        {
            // get the address of row j
            cv::Vec3b* data = bgr.ptr<cv::Vec3b>(j);
            uchar * pc = contours.ptr<uchar>(j);
            for (int i = 0; i < bgr.cols; i++)
            {
                // process each pixel ---------------------
                if(pc[i]) data[i] = color;
                // end of pixel processing ----------------
            }
        } // end of line
    }
    else if(bgr.channels() == 4)
    {
        cv::Vec4b c(color[0], color[1], color[2], color[3]);
        for (int j = 0; j < bgr.rows; j++)
        {
            // get the address of row j
            cv::Vec4b* data = bgr.ptr<cv::Vec4b>(j);
            uchar * pc = contours.ptr<uchar>(j);
            for (int i = 0; i < bgr.cols; i++)
            {
                // process each pixel ---------------------
                if(pc[i]) data[i] = c;
                // end of pixel processing ----------------
            }
        } // end of line
    }
}

void colorReduce(cv::Mat &image, int div)
{
    int nl = image.rows; // number of lines
    int nc = image.cols * image.channels();
    if (image.isContinuous())
    {
        // then no padded pixels
        nc = nc * nl;
        nl = 1; // it is now a 1D array
    }
    // this loop is executed only once
    // in case of continuous images
    for (int j = 0; j < nl; j++)
    {
        uchar* data = image.ptr<uchar>(j);
        for (int i = 0; i < nc; i++)
        {
            // process each pixel ---------------------
            data[i] = data[i] / div * div + div / 2;
            // end of pixel processing ----------------
        } // end of line
    }
}

bool mat32Fto8U(const cv::Mat &in, cv::Mat &out, double alpha, double beta)
{
    switch (in.channels())
    {
    case 1:
        in.convertTo(out, CV_8UC1, alpha, beta);
        break;
    case 3:
        in.convertTo(out, CV_8UC3, alpha, beta);
        break;
    case 4:
        in.convertTo(out, CV_8UC4, alpha, beta);
        break;
    default:
        return false;
        break;
    }
    return true;

}
bool contrastEnhance(const cv::Mat &in, cv::Mat &out)
{
    double minval, maxval, alpha, beta;
    cv::minMaxLoc(in, &minval, &maxval);
    double range = maxval - minval;
    switch (in.depth())
    {
    case CV_8U:
        alpha = 255.0 / range;
        beta = - minval * alpha;
        break;
    case CV_32F:
        alpha = 1 / range;
        beta = - minval * alpha;
        break;
    case CV_16U:
        alpha = 65536 / range;
        beta = - minval * alpha;
        break;
    case CV_16S:
        alpha = 32767.0 / range;
        beta = - minval * alpha -32767;
        break;
    case CV_8S:
        alpha = 128.0 / range;
        beta = - minval * alpha - 127;
        break;
    default:
        alpha = 1;
        beta = 0;
        break;
    }
    out = alpha * in + beta;

    return true;
}

void salt(cv::Mat &image, int N)
{
    std::default_random_engine generator;
    std::uniform_int_distribution<int> randomRow(0, image.rows-1);
    std::uniform_int_distribution<int> randomCol(0, image.cols-1);

    for (int k = 0; k < N; k++)
    {
        // random image coordinate
        int i = randomCol(generator);
        int j = randomRow(generator);

        if (image.type() == CV_8UC1)
        { // gray-level image

            // single-channel 8-bit image
            image.at<uchar>(j, i) = 255;

        } else if (image.type() == CV_8UC3)
        { // color image

            // 3-channel image
            image.at<cv::Vec3b>(j, i)[0] = 255;
            image.at<cv::Vec3b>(j, i)[1] = 255;
            image.at<cv::Vec3b>(j, i)[2] = 255;

            // or simply:
            // image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255);
        }
    }
}

// using kernel
void sharpen2D(const cv::Mat &image, cv::Mat &result)
{
    // Construct kernel (all entries initialized to 0)
    cv::Mat kernel(3,3,CV_32F,cv::Scalar(0));
    // assigns kernel values
    kernel.at<float>(1, 1)= 5.0;
    kernel.at<float>(0, 1)= -1.0;
    kernel.at<float>(2, 1)= -1.0;
    kernel.at<float>(1, 0)= -1.0;
    kernel.at<float>(1, 2)= -1.0;

    //filter the image
    cv::filter2D(image,result,image.depth(),kernel);
}
